package com.binarycat.dynamiclimit.initialization;

import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.util.CollectionUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import com.binarycat.dynamiclimit.DynamicLimiterManager;
import com.binarycat.dynamiclimit.bean.DynamicLimitData;
import com.binarycat.dynamiclimit.bean.MethodMappingLimitData;
import com.binarycat.dynamiclimit.collector.AbstractLimitDataCollector;
import com.binarycat.dynamiclimit.limiter.DynamicLimiter;
import com.binarycat.dynamiclimit.limiter.policy.AbstractLimitPolicy;
import com.binarycat.dynamiclimit.limiter.policy.CpuLoadLimitPolicy;
import com.binarycat.dynamiclimit.limiter.policy.ExceptionRateLimitPolicy;
import com.binarycat.dynamiclimit.limiter.policy.MemoryUsageRateLimitPolicy;
import com.binarycat.dynamiclimit.limiter.policy.TimeoutRateLimitPolicy;
import com.binarycat.dynamiclimit.util.LimitRateLogUtils;

/**
 * 动态限流初始化类
 * 
 * @author jinbaoji
 * @date 2018/08/08
 */
public class DynamicLimitInitialization {

    private final static Logger logger = LoggerFactory.getLogger(DynamicLimitInitialization.class);

    /**
     * 初始化限流信息
     * 
     * @param applicationContext
     * @param dynamicLimiterManager
     */
    public void initLimitInfo(ApplicationContext applicationContext, DynamicLimiterManager dynamicLimiterManager) {

        initBaseInfo(dynamicLimiterManager);

        initDynamicLimitPolicyList(applicationContext, dynamicLimiterManager);
        initMetric(applicationContext, dynamicLimiterManager);
        initMonitor(applicationContext, dynamicLimiterManager);
    }

    /**
     * 初始化基本信息(如日志开关)
     * 
     * @param dynamicLimiterManager
     */
    private void initBaseInfo(DynamicLimiterManager dynamicLimiterManager) {
        if (dynamicLimiterManager.getDynamicLimiterConfig().isNeedPrintLimitRateLog()) {
            LimitRateLogUtils.setLogswitch(true);
        }
    }

    /**
     * 初始化限流策略
     * 
     * @param dynamicLimiterManager
     * @param applicationContext
     */
    private void initDynamicLimitPolicyList(ApplicationContext applicationContext,
        DynamicLimiterManager dynamicLimiterManager) {
        LimitRateLogUtils.log("初始化限流策略");
        List<AbstractLimitPolicy> abstractDynamicLimitPolicyList = dynamicLimiterManager.getDynamicLimitPolicyList();

        abstractDynamicLimitPolicyList.add(new CpuLoadLimitPolicy());
        abstractDynamicLimitPolicyList.add(new MemoryUsageRateLimitPolicy());
        abstractDynamicLimitPolicyList.add(new TimeoutRateLimitPolicy());
        abstractDynamicLimitPolicyList.add(new ExceptionRateLimitPolicy());

        List<AbstractLimitPolicy> limitPolicyNameList =
            dynamicLimiterManager.getDynamicLimiterConfig().getLimitPolicyExtendList();
        if (!CollectionUtils.isEmpty(limitPolicyNameList)) {
            abstractDynamicLimitPolicyList.addAll(limitPolicyNameList);
        }

    }

    /**
     * 初始化限流监听器
     * 
     * @param applicationContext
     * @param dynamicLimiterManager
     */
    private void initMonitor(ApplicationContext applicationContext, DynamicLimiterManager dynamicLimiterManager) {
        DynamicLimitData limitDataTem = dynamicLimiterManager.getLimitData();
        Map<String, MethodMappingLimitData> methodMappingLimitDataMapTem = limitDataTem.getMethodMappingLimitDataMap();
        if (methodMappingLimitDataMapTem.isEmpty()) {
            LimitRateLogUtils.log("未标记限流注解,无需初始化限流监听器");
            return;
        }
        LimitRateLogUtils.log("初始化限流监听器");
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        Runnable limitRateMonitorTask = () -> {
            while (true) {
                DynamicLimiter dynamicLimiter = dynamicLimiterManager.getDynamicLimiter();
                AbstractLimitDataCollector limitDataCollector = dynamicLimiterManager.getLimitDataCollector();

                //收集信息
                limitDataCollector.collectSystemRuntimeInfo(dynamicLimiterManager);
                
                LimitRateLogUtils.showMetricInfo(dynamicLimiterManager);
                
                //执行限流策略
                dynamicLimiter.excuteDynamicLimitPolicy(dynamicLimiterManager);

                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    logger.error("limitRateMonitorTask异常", e);
                }
            }

        };
        executorService.execute(limitRateMonitorTask);
    }

    /**
     * 初始化限流度量信息和限流器
     * 
     * @param applicationContext
     * @param dynamicLimiterManager
     * @throws BeansException
     */
    private void initMetric(ApplicationContext applicationContext, DynamicLimiterManager dynamicLimiterManager)
        throws BeansException {
        logger.info("初始化限流度量信息和限流器");
        DynamicLimitData limitData = dynamicLimiterManager.getLimitData();

        RequestMappingHandlerMapping requestMappingHandlerMapping =
            applicationContext.getBean(RequestMappingHandlerMapping.class);

        if (requestMappingHandlerMapping == null) {
            return;
        }
        Map<RequestMappingInfo, HandlerMethod> handlerMethodmap = requestMappingHandlerMapping.getHandlerMethods();
        if (handlerMethodmap == null) {
            return;
        }

        // 注册全局的度量器
        MetricFactory.registryGlobalMetric(limitData);

        for (Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethodmap.entrySet()) {
            HandlerMethod handlerMethod = entry.getValue();
            boolean status = MetricFactory.registryMetric(limitData, handlerMethod);
            if (status) {
                RateLimiterFactory.registryRateLimiter(limitData, handlerMethod);
            }
        }
    }

}
